#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "lib/collections.h"
#include "lib/testing.h"

int runtest();

int main(void)
{
	if(getenv("ONLY_TEST") != NULL)
					return runtest();

	return EXIT_SUCCESS;
}


int comparestr(void* input_a, void * input_b)
{
	char* value_a = (char*) input_a;
	char* value_b = *(char**) input_b;
	return strcmp(value_a, value_b);
}


static struct lala
{
	int a;
} lele;

int runtest()
{
	lele.a = 12;

	int valor1 = 22;
	int valor2 = 11;
	int valorfake = 123456;

	stack pila = stack_create();

	stack_push(pila, valor1);
	stack_push(pila, valor2);

	stack_pop(pila, valor1);
	stack_pop(pila, valor2);

	assert_int_equals(11, valor1, "El ultimo elemento de la pila no salio primero");
	assert_int_equals(22, valor2, "El primer elemento de la pila no salio ultimo");

	stack_delete(pila);


	list lista = list_create();

	list_append(lista,valor1);

	assert_is_true(list_contains(lista, valor1, NULL), "La lista debe contener el elemento recien ingresado");

	list_append(lista,valor2);

	assert_int_equals(1, list_indexof(lista, valor2, NULL), "El segundo valor ingresado debe esatr en el indice 1");

	assert_int_equals(-1, list_indexof(lista, valorfake, NULL), "El valor dado a indexof no existe y deberia devolver -1");

	list_read(lista, 0, valor1);
	list_read(lista, 1 , valor2);

	assert_int_equals(11, valor1, "El elemento del indice 0 de la lista debe ser el primero ingresado");
	assert_int_equals(22, valor2, "El elemento del indice 1 de la lista debe ser el segundo ingresado");

	list_readfirst(lista, valor1);
	list_readlast(lista, valor2);

	assert_int_equals(11, valor1, "El primer elemento de la lista debe ser el primero ingresado");
	assert_int_equals(22, valor2, "El ultimo elemento de la lista debe ser el segundo ingresado");

	list_removefirst(lista);
	list_read(lista, 0, valor2);
	assert_int_equals(22, valor2, "Luego de quitar el primer elemento de la lista el segundo debe ser el primero");

	list_prepend(lista, valor1);
	list_read(lista, 0, valor1);
	assert_int_equals(11, valor1, "El valor agregado con prepend debe estar al principio");


	list_removeat(lista, 1);
	list_prepend(lista, valor2);
	list_read(lista, 1, valor1);

	assert_int_equals(11, valor1, "Quite el ultimo elemento por indice y lo puse alprincio, los dos deben estar volteados");

	assert_int_equals(2, list_count(lista), "A este punto la lista debe tener dos elementos");


	list_removelast(lista);
	list_read(lista, 0, valor2);
	assert_int_equals(22, valor2, "Quitando el ultimo valor de la lista deberia solo quedarme el primero");

	list_clear(lista);

	assert_is_true(list_isempty(lista), "Luego de limpar la lista esta debe esar vacia");


	//Para observer. La lista se limpia sola al eliminarla
	list_append(lista, valor1);
	list_append(lista, valor2);

	int indice, actual;

	list_foreach(lista, indice, actual)
	{
		if(indice == 0)
		{
			assert_int_equals(11, actual, "El primer elemento del foreach debe coincider con el indice");
		}
		else
		{
			assert_int_equals(22, actual, "El segundo elemento del foreach debe coincider con el indice");
		}
	}

    int valor3 = 33;
    list_insert(lista, 1, valor3);

    list_read(lista, 1, valor3);
    list_read(lista, 2, valor2);

    assert_int_equals(33, valor3, "Luego de insartar un elemento en una posicion debe estar en el indicado");
    assert_int_equals(22, valor2, "Al insertar un valor en la lista, el que estaba en su lugar debe ser desplazado");

    int matchs = 0;
    list_where(lista, indice, actual, actual == 33)
    	matchs++;

    assert_int_equals(1, matchs, "El valor existe una sola vez y tiene que pasar solo una por el bucle");

	//la lista de limpia sola al borrarse
	list_delete(lista);

	//Prueba con cadenas avanzada con cadenas
	char * cadena1 = malloc(sizeof(char) * 4);
	char * cadena2 = malloc(sizeof(char) * 4);
	char * cadena3 = malloc(sizeof(char) * 4);

	strcpy(cadena1, "uno");
	strcpy(cadena2, "dos");
	strcpy(cadena3, "uno");

	//ver comparador arriba

	list lista2 = list_create();

	list_append(lista2, cadena1);
	list_append(lista2, cadena2);

	assert_is_true(list_contains(lista2,cadena3, &comparestr), "El valor comparado con la funcion debe ser positivo");

	char * cadenaactual;
	int indicecadena;

	list_foreach(lista2, indicecadena, cadenaactual)
	{
		free(cadenaactual);
	}


	free(cadena3);

	list_delete(lista2);


	//Definition
	int value = 4;
	int key = 0;
	int newValue = 10;
	int newKey = 25;
	int readKey;
	int readValue;
	int keyIs;
	int valueIs;
	int defIs;
	def auxDef;

	//se crea una definicion
	def newDef = def_create(sizeof(key),sizeof(value),&key,&value,NULL);
	//se lee la clave y el valor de newDef
	def_readKey(newDef,sizeof(readKey),&readKey);
	def_readValue(newDef,sizeof(readKey),sizeof(readValue),&readValue);
	//se testea que al leer key y value tengan los mismos valores seteados
	assert_int_equals(0,readKey, "La clave de la definición debe tener el mismo valor luego de crear y leer la definición");
	assert_int_equals(4,readValue, "El valor de la definición debe tener el mismo valor luego de crear y leer la definición");
	//se verifica que la clave sea = a key y el valor = value
	keyIs = def_isKey(newDef,sizeof(key),&key);
	valueIs = def_isValue(newDef,sizeof(key),sizeof(value),&value);
	defIs = def_isDef(newDef,sizeof(key),sizeof(value),&key,&value);
	//testea la verificación
	assert_is_true(keyIs, "La clave de la definición debe ser la clave agregada");
	assert_is_true(valueIs, "El valor de la definición debe ser el valor agregado");
	assert_is_true(defIs, "La definición debe ser d la clave y el valor agregado");
	//se modifica key y value de la def
	def_writeKey(newDef,sizeof(newKey),&newKey);
	def_writeValue(newDef,sizeof(newKey),sizeof(newValue),&newValue);
	//se lee key y value
	def_readKey(newDef,sizeof(readKey),&readKey);
	def_readValue(newDef,sizeof(readKey),sizeof(readValue),&readValue);
	//se testea que hayan cambiado los valores de key y value
	assert_int_notEquals(0,readKey, "La clave de la definición debe tener distinto valor luego de cambiar la clave");
	assert_int_notEquals(4,readValue, "El valor de la definición debe tener distinto valor luego de cambiar el valor");
	//se crea una nueva definicion con los primeros valores, y además se apunta la primer definicion a esta
	def newDef2 = def_create(sizeof(key),sizeof(value),&key,&value,NULL);
	def_connect(newDef,newDef2);
	//se obtiene la definicion apuntada por la primer definicion
	auxDef = def_getlink(newDef);
	//se verifica que ya no apunte a null
	assert_is_notNull(auxDef,"La definición luego de ser apuntada no debe apuntar a NULL");
	//se lee los valores donde apuntan la definicion obtenida
	def_readKey(auxDef,sizeof(readKey),&readKey);
	def_readValue(auxDef,sizeof(readKey),sizeof(readValue),&readValue);
	//se verifica que tanga los datos iniciales
	assert_int_equals(0,readKey, "La clave de la definición debe tener el mismo valor luego de crear y leer la definición");
	assert_int_equals(4,readValue, "El valor de la definición debe tener el mismo valor luego de crear y leer la definición");
	//se eliminan las definiciones
	newDef = def_delete(newDef);
	newDef2 = def_delete(newDef2);
	//se verifica que apunten a null
	assert_is_null(newDef,"La definición luego de ser eliminada debe apuntar a NULL");
	assert_is_null(newDef2,"La definición luego de ser eliminada debe apuntar a NULL");

	//Dicionary
	dictionary d;
	dictionary_iterator i;
	int keyM = 11;
	int valueM = 4;
	int keyF = 10;
	int valueF = 7;
	int couldAdd = 0;
	int keyAux;
	int valueAux;

	//se crea un diccionario
	d = dictionary_create();
	//se prueba que se haya creado bien
	assert_is_notNull(d,"El diccionario luego de ser creado no debe apuntar a NULL");
	//se testea que el diccionario este vacio
	assert_is_true(dictionary_isEmpty(d),"El diccionario no debe tener definiciones si no se le agregan");
	//se agregan datos al diccionario
	couldAdd = dictionary_add(d, keyM, valueM);
	//se testea que puedas agregar el dato la primer vez
	assert_is_true(couldAdd == 0,"La definición debe poder agregar un elemento cuando el diccionario este vacio");
	//se intenta agregar otra definicion con igual clave
	couldAdd = dictionary_add(d, keyM, valueF);
	//se testea que no lo agregue de nuevo
	assert_is_true(couldAdd == -1,"La definición no debe poder agregar un elemento si ya existe una deficion con la misma clave");
	//se agrega una nueva definición valida
	couldAdd = dictionary_add(d, keyF, valueF);
	int primero = 1;
	dictionary_foreach(d, i.iterator, keyAux, valueAux)
	{
		if(primero)
		{
			assert_int_equals(10,keyAux, "La clave de la definición debe tener el mismo valor luego de crear el diccionario y leer la definición");
			assert_int_equals(7,valueAux, "El valor de la definición debe tener el mismo valor luego de crear el diccionario  y leer la definición");
			primero--;
		}
		else
		{
			assert_int_equals(11,keyAux, "La clave de la definición debe tener el mismo valor luego de crear el diccionario y leer la definición");
			assert_int_equals(4,valueAux, "El valor de la definición debe tener el mismo valor luego de crear el diccionario  y leer la definición");
		}

	}
	//obtengo el valor de keyM
	dictionary_getValue(d, keyM, valueAux);
	//compraro que tenga el mismo valor al agregado
	assert_int_equals(4,valueAux, "El valor de la definición debe tener el mismo valor luego de crear el diccionario  y leer la definición");
	//actualizo el valor de keyM a 10;
	int ultimateValue = 99;
	dictionary_update(d, keyM, ultimateValue);
	//obtengo el valor de keyM
	dictionary_getValue(d, keyM, valueAux);
	//compraro que tenga el mismo valor al actualizado
	assert_int_equals(99,valueAux, "El valor de la definición debe tener el mismo valor luego de actualizar el diccionario  y leer la definición");
	//elimino la keyM
	dictionary_remove(&d,keyM);
	//obtengo el valor de keyM y compruebo que no exista esa key
	assert_is_true(dictionary_getValue(d, keyM, valueAux) == -1,"Una vez removido el valor no debe de podes actualizarse, ya que no se encuenta en él");
	//agrego la definicion keyM, valueM
	dictionary_write(d,keyM,valueM);
	//obtengo el valor de keyM y compruebo que exista esa key
	dictionary_getValue(d, keyM, valueAux);
	//se testea que el valor de la definicion sea el mismo luego de ser agregado
	assert_int_equals(4,valueAux,"El valor debe poder agregarse si no existe la clave y si existe modificarla");
	//se elimina el diccionario
	dictionary_delete(&d);
	//se testea que el diccionario siga siendo nulo
	assert_is_null(d,"El diccionario luego de ser eliminado debe apuntar a NULL");

	//Ejemplo de funcionamiento de assert
	//assert_int_equals(10,10, "El test paso correctamente el mensaje no aparece");
	//assert_int_equals(0,1, "Fuerzo un error para mostrar como se ve");


	//Se muestra el detalle del test
	testing_dump();

	return 0;
}
